#ifndef VENUS_GEOMETRY_CAMERA_H
#define VENUS_GEOMETRY_CAMERA_H

#include <iostream>
#include "matrix.h"
#include "point.h"

namespace venus {	
class CameraBase {
public:			
	typedef matrix Matrix4;

	virtual ~CameraBase(void) { };
	//rotate on right vector
	void Pitch(float angle) {
		look_ = this->Rotate(right_, look_, angle);
		this->UpdateUp();
	}
	//rotate on up vector
	void Yaw(float angle) {
		right_ = this->Rotate(up_, right_, angle);	
		this->UpdateLook();
	}
	//rotate on look vector	
	void Roll(float angle) {
		up_ = this->Rotate(look_, up_, angle);
		this->UpdateRight();
	}
	float GetFovy(void) const { return fovy_; }
	void SetFovy(float fovy) { fovy_ = fovy; }
	float GetAspectRatio(void) const { return aspect_ratio_; }
	void SetAspectRatio(float aspect_ratio) { 
		aspect_ratio_ = aspect_ratio; 
	}
	const Vector3& right(void) const { return right_; }
	const Vector3& up(void) const { return up_; }
	const Vector3& look(void) const { return look_; }
	const Point& eye_pos(void) const { return eye_pos_; }
protected:
	void set_look(const Vector3& look) { 
		look_ = Normalize(look); 
		this->UpdateRight();
		this->UpdateUp();
	}
	void set_up(const Vector3& up) { 
		up_ = Normalize(up); 
		this->UpdateRight();
		this->UpdateUp();
	}
	void set_eye_pos(const Point& eye_pos) { eye_pos_ = eye_pos; }
	Vector3 Rotate(const Vector3& axis, const Vector3& v, 
			float angle) {
		Matrix4 mat = mat4RotateAxis(axis, 
			Math::deg2rad(angle));
		return Normalize(vec3Transform(v, mat));
	}
	void UpdateLook(void) { 
		look_ = Normalize(Cross(up_, right_));
	}
	void UpdateUp(void) {
		up_ = Normalize(Cross(right_, look_));
	}
	inline void UpdateRight(void) {
		right_ = Normalize(Cross(look_, up_));
	}
private:	
	Vector3 right_;
	Vector3 up_;
	Vector3 look_;
	Point eye_pos_;
	float fovy_;
	float aspect_ratio_;
};

class OrbitCamera : public CameraBase {
public:	
	OrbitCamera(const Point& eye_pos, const Point& target,
		const Vector3& up = Vector3::kUnitY) {
		this->set_eye_pos(eye_pos);
		this->set_target(target);
		this->set_up(up);
	}

	void Zoom(float units) { 
		CameraBase::set_eye_pos(this->eye_pos() + units * this->look());
	}

	void set_target(const Point& target) {
		target_ = target;
		this->set_look(this->target() - this->eye_pos());
	}

	void set_eye_pos(const Point& eyePos) {
		CameraBase::set_eye_pos(eyePos);
		this->set_look(this->target() - this->eye_pos());
	}

	void set_up(const Vector3& up) {
		CameraBase::set_up(up); 
	}

	void Yaw(float angle) { 
		CameraBase::Yaw(angle);
		this->UpdateEyePos();
	}

	void Pitch(float angle) {
		CameraBase::Pitch(angle); this->UpdateEyePos();
	}

	void Roll(float angle) {
		CameraBase::Roll(angle); this->UpdateEyePos();
	}

	const Point& target(void) const { return target_; }

protected:
	void UpdateEyePos(void) { 
		const Vector3& dist = this->target() 
			- this->eye_pos();
		CameraBase::set_eye_pos(this->target() 
			- this->look() * dist.Length());
	}
	
protected:
	Point target_;
};
}

#endif//VENUS_GEOMETRY_CAMERA_H
